package classification.calcSimilarity;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;

import classification.elemListRepr.XMLElement;
import classification.elemListRepr.XMLElementList;
import classification.slvm.StructSimilarity;
import classification.slvm.TextSimilarity;

import preprocess.Arith;
import preprocess.DBWrapper;
import preprocess.XMLDoc;

import utils.Configuration;
import static utils.Configuration.*;

/**
 * 该类继承自CalcSimilarity，实现了通过SLVM每两个对应结构出相似关联度的XML文档相似度计算
 * 
 * @author eDisOn
 * 
 */
public class CalcSimilarityBySLVM extends CalcSimilarity
{
    private StructSimilarity                                 STRUCT_SIMILAIRTY = null;

    private HashMap<String, HashMap<String, TextSimilarity>> TEXT_SIMILARITY   = null;

    private final String                                     DBName            = Configuration.SIMILARITY.struct_similarity_database;

    private final String                                     structSimiID      = Configuration.SIMILARITY.feature_struct_similarity;

    public CalcSimilarityBySLVM()
    {
        TEXT_SIMILARITY = new HashMap<String, HashMap<String, TextSimilarity>>();
    }

    /**
     * @see similarity.Similarity#calcSimilarity(preprocess.XMLDoc,
     *      preprocess.XMLDoc)
     */
    public double calcSimilarity(XMLDoc doc1, XMLDoc doc2)
    {
        double similarity = 0.0;

        if (this.similarityRecord.containsKey(doc1.getDocID()))
        {
            if (this.similarityRecord.get(doc1.getDocID()).containsKey(
                    doc2.getDocID()))
            {
                similarity = this.similarityRecord.get(doc1.getDocID()).get(
                        doc2.getDocID());
                return similarity;
            }
        }

        XMLElementList firXMLELReprList = this.loadXMLEList(doc1);
        XMLElementList secXMLELReprList = this.loadXMLEList(doc2);

        similarity = this.calcSimilarity(firXMLELReprList, secXMLELReprList);

        /* 保存备忘录 */
        if (this.similarityRecord.containsKey(doc1.getDocID()))
        {
            this.similarityRecord.get(doc1.getDocID()).put(doc2.getDocID(),
                    similarity);
        }
        else
        {
            HashMap<String, Double> map = new HashMap<String, Double>();
            map.put(doc2.getDocID(), similarity);
            this.similarityRecord.put(doc1.getDocID(), map);
        }

        if (this.similarityRecord.containsKey(doc2.getDocID()))
        {
            this.similarityRecord.get(doc2.getDocID()).put(doc1.getDocID(),
                    similarity);
        }
        else
        {
            HashMap<String, Double> map = new HashMap<String, Double>();
            map.put(doc1.getDocID(), similarity);
            this.similarityRecord.put(doc2.getDocID(), map);
        }

        return similarity;
    }

    /**
     * 计算两个XMLElementList所表示的XML文档的相似度
     * 
     * @param list1
     *            待计算的第一个XMLElementList对象
     * @param list2
     *            待计算的第二个XMLElementList对象
     * 
     * @return 所指定的两个XMLElementList对象所表示的XML文档的相似度
     */
    private double calcSimilarity(XMLElementList list1, XMLElementList list2)
    {
        /*
         * 因为这里为了节省空间，而且由于XML A与B 的相似度等于B与 A的相似度，所以按照出现的先后次序，只有Similarity[a][b]
         * (a <b)被记录了，可是由于在计算文档的整个相似度的时候也是采用这种方式，所以不会出现访问相似度矩阵的时候为空的情况。
         * 不过可能会在以后的调用中出现潜在问题
         */
        double similarity = 0.0;

        /* 首先计算这两个XML之间的文本相似度，并把计算结构保存到备忘录中 */
        TextSimilarity textSimilarity = this.getTextSimilarity(list1, list2);

        /* 从文件或内存中导入已有的结构相似度矩阵 */
        StructSimilarity structSimilarity = this.getStructSimilarity();

        /* 计算两个XML的相似度 */
        Set<Integer> textKeySet = textSimilarity.keySet();
        Set<Integer> structKeySet = structSimilarity.keySet();
        double textSimiForElem = 0.0;
        double structSimiForElem = 0.0;

        /* 通过循环计算文本相似度和结构相似度中都不为0的元素之间的相似度，然后将这些相似度相加作为整体的相似度 */
        for (Integer key : textKeySet)
        {
            if (structKeySet.contains(key))
            {
                int firIndex = key & 255;
                int secIndex = (key >> 8) & 255;

                textSimiForElem = textSimilarity.getSimilarity(firIndex,
                        secIndex);
                structSimiForElem = structSimilarity.getSimilarity(firIndex,
                        secIndex);

                similarity += Arith.round(textSimiForElem * structSimiForElem,
                        8);
            }
        }
        return similarity;
    }

    /**
     * 首先查看备忘录看这两个XML之间的文本相似度矩阵是否已经计算，如果算了的话之间返回，不在的话计算完后保存备忘录后返回
     * 
     * @param firList
     *            first XMLElementList object
     * @param secList
     *            second XMLElementList object
     * @return the element text similarity matrix for this two xml document
     */
    private TextSimilarity getTextSimilarity(XMLElementList firList,
            XMLElementList secList)
    {
        /* 首先判断这两个XML之间的文本相似度矩阵是否已经在备忘录中，如果在的话之间返回，不在的话计算完后保存备忘录 */
        if (this.TEXT_SIMILARITY.containsKey(firList.getListID()))
        {
            HashMap<String, TextSimilarity> textSimiArr = this.TEXT_SIMILARITY
                    .get(firList.getListID());

            /* 查看firList的备忘录中是否已经有该两个XML之间文本相似度的记录F */
            if (textSimiArr.containsKey(secList.getListID()))
            {
                return textSimiArr.get(secList.getListID());
            }
            else
            {
                // DO NOTHING!
            }
        }

        /* 这个矩阵用来保存firList和secList中的所有元素之间对应的文本相似度 */
        TextSimilarity textSimiMatrix = new TextSimilarity();

        int firListVocNum = firList.getTotalVocNum();
        int secListVocNum = secList.getTotalVocNum();
        int elemNum = firList.getElemNum();

        /* 循环计算每两个Element之间的相似度 */
        for (int first = 0; first < elemNum; first++)
        {
            /* 开始计算 第一个XML中的Element与第二个XML中的所有Element之间的文本相似度 */
            XMLElement firElem = firList.get(first);
            if (firElem.getVocNum() == 0)
            {
                continue;
            }
            else
            {
                double firProportion = ((double) firElem.getVocNum() / (double) firListVocNum);
                for (int second = 0; second < elemNum; second++)
                {
                    XMLElement secElem = secList.get(second);
                    if (secElem.getVocNum() == 0)
                    {
                        continue;
                    }
                    else
                    {
                        double secProportion = ((double) secElem.getVocNum() / (double) secListVocNum);

                        /* 计算两个元素firElem和secElem之间的相似度 */
                        double simi = this.calcElemSimilarity(firElem,
                                firProportion, secElem, secProportion);

                        if (simi != 0)
                        {
                            textSimiMatrix.addSimilarity(first, second, simi);
                        }
                    }
                }
            }
        }

        /* 保存firList的备忘录 */
        if (this.TEXT_SIMILARITY.containsKey(firList.getListID()))
        {
            /*
             * 因为是循环计算第一个XML与其他所有XML之间的相似度，
             * 所以如果该第一个XML（firList）已经被计算过了，则备忘录中将有其部分相似度结果， 直接将新的计算结果加到相似度结果数组末尾
             */
            HashMap<String, TextSimilarity> textSimiArr = this.TEXT_SIMILARITY
                    .get(firList.getListID());
            textSimiArr.put(secList.getListID(), textSimiMatrix);
        }
        else
        {
            /* firList第一次被计算和其他XML的相似度，创建记录其相似度的数组 */
            HashMap<String, TextSimilarity> textSimiArr = new HashMap<String, TextSimilarity>();
            textSimiArr.put(secList.getListID(), textSimiMatrix);
            this.TEXT_SIMILARITY.put(firList.getListID(), textSimiArr);
        }

        /* 保存secList的备忘录 */
        if (this.TEXT_SIMILARITY.containsKey(secList.getListID()))
        {
            HashMap<String, TextSimilarity> textSimiArr = this.TEXT_SIMILARITY
                    .get(secList.getListID());
            textSimiArr.put(firList.getListID(), textSimiMatrix);
        }
        else
        {
            HashMap<String, TextSimilarity> textSimiArr = new HashMap<String, TextSimilarity>();
            textSimiArr.put(firList.getListID(), textSimiMatrix);
            this.TEXT_SIMILARITY.put(secList.getListID(), textSimiArr);
        }

        /* 返回文本相似度矩阵 */
        return textSimiMatrix;
    }

    /**
     * Calculate each two XMLElements' similarity only according to their text
     * tfidf similarity score
     * 
     * @param firElem
     *            first XMLElement object
     * @param firElemWeight
     *            first XMLElement score weight in its XML document
     * @param secElem
     *            second XMLElement object
     * @param secElemWeight
     *            second XMLElement score weight in its XML document
     * @return their text tfidf similarity score
     */
    private double calcElemSimilarity(XMLElement firElem, double firElemWeight,
            XMLElement secElem, double secElemWeight)
    {
        double elemSimilarity = 0.0;

        HashMap<String, Double> firVocList = firElem.getVocabularyList();
        HashMap<String, Double> secVocList = secElem.getVocabularyList();
        if (firVocList != null && secVocList != null)
        {
            Set<String> firKeySet = firVocList.keySet();
            for (Iterator<String> firVocIt = firKeySet.iterator(); firVocIt
                    .hasNext();)
            {
                String firKey = firVocIt.next();
                double firValue = firVocList.get(firKey);
                if (secVocList.containsKey(firKey))
                {
                    double secValue = secVocList.get(firKey);
                    elemSimilarity += firValue * secValue;
                }
            }
        }
        double weight = firElemWeight * secElemWeight;
        elemSimilarity = elemSimilarity * weight;

        return Arith.round(elemSimilarity, 8);
    }

    private StructSimilarity getStructSimilarity()
    {
        /* 从数据库中查找已有的结构相似度有矩阵 */
        if (this.STRUCT_SIMILAIRTY == null)
        {
            DBWrapper db = new DBWrapper();
            StructSimilarity ss = (StructSimilarity) db.queryElemSimiMatrix(
                    this.DBName, this.structSimiID);

            if (ss != null)
            {
                this.STRUCT_SIMILAIRTY = ss;
            }
            else
            {
                // 如果数据库中没有结构相似度矩阵，则创建相似度矩阵模板返回
                StructSimilarity template = new StructSimilarity();
                template.addSimilarity(0, 0, 1);
                this.STRUCT_SIMILAIRTY = template;
            }
        }
        return this.STRUCT_SIMILAIRTY;
    }

    public void setStructSimilarity(final StructSimilarity ss)
    {
        this.STRUCT_SIMILAIRTY = ss;
    }
}
